package com.ideaaedi.springcloud.jd.user.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonds.constant.JdSymbolConstant;
import com.ideaaedi.springcloud.jd.commonds.entity.BaseCodeMsgEnum;
import com.ideaaedi.springcloud.jd.commonds.entity.PageInfo;
import com.ideaaedi.springcloud.jd.commonds.event.NeedAddHisEvent;
import com.ideaaedi.springcloud.jd.commonds.event.NeedDeleteHisEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysDeptAddedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysDeptDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysDeptUpdatedEvent;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysDeptHisPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysDeptPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.SysDeptAddReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.SysDeptListReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.req.SysDeptUpdateReqVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.SysDeptDetailRespVO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.SysDeptListRespVO;
import com.ideaaedi.springcloud.jd.commonds.exception.BaseException;
import com.ideaaedi.springcloud.jd.commonspring.config.properties.TenantProperties;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.ServiceImplExt;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.tenant.TenantDataScope;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.tenant.TenantDataScopeBO;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.tenant.TenantProvider;
import com.ideaaedi.springcloud.jd.commonspring.util.JdContextUtil;
import com.ideaaedi.springcloud.jd.commonspring.validator.SysValidator;
import com.ideaaedi.springcloud.jd.commonspring.validator.ValidId;
import com.ideaaedi.springcloud.jd.user.mapper.SysDeptMapper;
import com.ideaaedi.springcloud.jd.user.service.SysDeptService;
import com.ideaaedi.springcloud.jd.user.service.SysTenantService;
import com.ideaaedi.springcloud.jd.user.util.SysUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 系统-组织架构表（部门表） 服务实现类
 * </p>
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.A
 */
@Slf4j
@Service
public class SysDeptServiceImpl extends ServiceImplExt<SysDeptMapper, SysDeptPO> implements SysDeptService {
    
    @Resource
    private TenantProperties tenantProperties;
    
    @Resource
    private ApplicationContext applicationContext;
    
    @Resource
    private SysTenantService sysTenantService;
    
    @Override
    public PageInfo<SysDeptListRespVO> list(SysDeptListReqVO req) {
        String name = req.getName();
        Integer pid = req.getPid();
        Integer type = req.getType();
        String code = req.getCode();
        IPage<SysDeptPO> pageInfo = new Page<>(req.getPageNum(), req.getPageSize());
        IPage<SysDeptPO> page = this.page(pageInfo, new LambdaQueryWrapper<SysDeptPO>()
                .eq(pid != null, SysDeptPO::getPid, pid)
                .eq(type != null, SysDeptPO::getType, type)
                .like(StringUtils.isNotBlank(name), SysDeptPO::getName, name)
                .like(StringUtils.isNotBlank(code), SysDeptPO::getCode, code)
                .last("ORDER BY IFNULL(sort, " + Integer.MAX_VALUE + ") ASC, path ASC")
        );
        List<SysDeptPO> records = page.getRecords();
        
        // 获取父部门id & 部门名称
        Map<Integer, String> idAndNameMap;
        if (CollectionUtils.isEmpty(records)) {
            idAndNameMap = Collections.emptyMap();
        } else {
            idAndNameMap = list(new LambdaQueryWrapper<SysDeptPO>()
                    .select(SysDeptPO::getId, SysDeptPO::getName)
                    .in(SysDeptPO::getId, records.stream().map(SysDeptPO::getId).collect(Collectors.toSet()))
            ).stream().collect(Collectors.toMap(SysDeptPO::getId, SysDeptPO::getName));
        }
    
        // 处理 path和namepath
        String tenant = JdContextUtil.currUserTenantNonNull();
        boolean isBelongBizTenant = sysTenantService.belongBizTenant(tenant);
        List<String> pathList = records.stream().map(SysDeptPO::getPath).collect(Collectors.toList());
        Map<String, String> pathAndShrinkPathMap = SysUserUtil.shrinkDeptPathIfNecessary(tenant, isBelongBizTenant, tenantProperties.getBizTenantLevel(), pathList);
        Map<String, String> shrinkPathAndNamePathMap = mappingDeptNamePath(pathAndShrinkPathMap.values());
        
        // 转换为resp
        List<SysDeptListRespVO> list = records.stream().map(po -> {
            SysDeptListRespVO resp = new SysDeptListRespVO();
            BeanUtils.copyProperties(po, resp);
            resp.setName(idAndNameMap.get(resp.getPid()));
            String shrinkPath = pathAndShrinkPathMap.get(po.getPath());
            resp.setPath(shrinkPath);
            resp.setNamePath(shrinkPathAndNamePathMap.get(shrinkPath));
            return resp;
        }).collect(Collectors.toList());
        return PageInfo.of(page.getTotal(), req.getPageNum(), req.getPageSize(), list);
    }
    
    @Override
    public SysDeptDetailRespVO detail(Integer deptId) {
        Objects.requireNonNull(deptId, "deptId cannot be null.");
        SysDeptPO po = getById(deptId);
        if (po == null) {
            return null;
        }
        return detail(po);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysDeptDetailRespVO update(SysDeptUpdateReqVO req) {
        Integer id = req.getId();
        Integer newPid = req.getPid();
        Integer type = req.getType();
        String code = req.getCode();
        String name = req.getName();
    
        SysDeptPO dbPo = getById(id);
        SysDeptPO oldPo = new SysDeptPO();
        BeanUtils.copyProperties(dbPo, oldPo);
        Integer oldPid = oldPo.getPid();
        if (StringUtils.isBlank(name)) {
            name = oldPo.getName();
            req.setName(name);
        }
        if (StringUtils.isBlank(code)) {
            code = oldPo.getCode();
            req.setCode(code);
        }
        if (type == null) {
            type = oldPo.getType();
            req.setType(type);
        }
        
        SysDeptPO po = new SysDeptPO();
        BeanUtils.copyProperties(req, po, BaseConstant.TENANT);
        
        if (newPid != null && !newPid.equals(oldPid)) {
            Assert.isTrue(!newPid.equals(id), "pid cannot equal id.");
            String newPath;
            if (newPid == 0) {
                newPath = id + JdSymbolConstant.COMMA;
            } else {
                SysDeptPO parenDept = getById(newPid);
                if (parenDept == null) {
                    throw new BaseException("dept id -> " + newPid,
                            BaseCodeMsgEnum.DATA_NON_EXIST_OR_NON_DATA_PERMISSION);
                }
                boolean pidDeptIsSubDept = Arrays.stream(parenDept.getPath().split(JdSymbolConstant.COMMA))
                        .filter(StringUtils::isNotBlank).map(Integer::parseInt).anyMatch(x -> Objects.equals(id, x));
                if (pidDeptIsSubDept) {
                    throw new BaseException(BaseCodeMsgEnum.PARENT_DEPT_CANNOT_BE_SUB_DEPT);
                }
                newPath = parenDept.getPath() + id + JdSymbolConstant.COMMA;
            }
            
            po.setPath(newPath);
            String tenant = newPath;
            SysValidator.verifyCouldUpdateThisTenant(tenant);
            po.setTenant(tenant);
        }
        
        // 更新部门
        TenantProvider.voidExecGlobal(() -> updateById(po));
        
        // 发布事件
        SysDeptPO lastPo = getById(id);
        applicationContext.publishEvent(new SysDeptUpdatedEvent(oldPo, lastPo));
        if (!Objects.equals(oldPo, lastPo)) {
            applicationContext.publishEvent(new NeedAddHisEvent(Lists.newArrayList(lastPo), SysDeptHisPO.class));
        }
        return detail(lastPo);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysDeptDetailRespVO add(SysDeptAddReqVO req) {
        String code = req.getCode();
        String remark = req.getRemark();
        String name = req.getName();
        Integer pid = req.getPid();
        Integer sort = req.getSort();
        Integer type = req.getType();
        SysDeptPO po = new SysDeptPO();
        po.setName(name);
        po.setPid(pid);
        po.setPath(BaseConstant.EMPTY);
        po.setCode(code);
        po.setType(type);
        po.setSort(sort);
        po.setRemark(remark);
        save(po);
        Integer id;
        // 自动生成的id, 不能为系统id保留值
        Integer generateId = po.getId();
        boolean needUpdateId = generateId <= BaseConstant.SYS_INTERNAL_DATA_ID_EQ;
        if (needUpdateId) {
            id = (int)(BaseConstant.SYS_INTERNAL_DATA_ID_EQ + 1);
            TenantProvider.voidExecGlobal(() ->
                    update(new SysDeptPO(), new LambdaUpdateWrapper<SysDeptPO>()
                            .set(SysDeptPO::getId, id)
                            .eq(SysDeptPO::getId, generateId))
            );
        } else {
            id = generateId;
        }
        
        // 更新Path 和 对应的所属租户
        String deptPath;
        if (pid == 0) {
            deptPath = id + JdSymbolConstant.COMMA;
        } else {
            SysDeptPO parenDept = getById(pid);
            if (parenDept == null) {
                throw new BaseException("dept id -> " + pid, BaseCodeMsgEnum.DATA_NON_EXIST_OR_NON_DATA_PERMISSION);
            }
            deptPath = parenDept.getPath() +  id + JdSymbolConstant.COMMA;
        }
        // 推断租户值并校验当前用户有操作该租户的数据的权限
        String tenant = deptPath;
        SysValidator.verifyCouldUpdateThisTenant(tenant);
        
        TenantProvider.voidExecGlobal(() ->
            update(new SysDeptPO(), new LambdaUpdateWrapper<SysDeptPO>()
                    .set(SysDeptPO::getPath, deptPath)
                    .set(SysDeptPO::getTenant, tenant)
                    .eq(SysDeptPO::getId, id))
        );
        
        // 发布事件
        po = getById(id);
        applicationContext.publishEvent(new SysDeptAddedEvent(po));
        applicationContext.publishEvent(new NeedAddHisEvent(Lists.newArrayList(po), SysDeptHisPO.class));
        return detail(po);
    }
    
    @Override
    @ValidId
    @Transactional(rollbackFor = Exception.class)
    public SysDeptDetailRespVO delete(Integer deptId) {
        SysDeptPO anySubDept = getOne(new LambdaQueryWrapper<SysDeptPO>()
                .select(SysDeptPO::getId)
                .eq(SysDeptPO::getPid, deptId)
                .last("limit 1")
        );
        if (anySubDept != null) {
            throw new BaseException(BaseCodeMsgEnum.EXIST_SUB_DEPT);
        }
        
        SysDeptPO deptPo = getById(deptId);
        SysDeptDetailRespVO resp = detail(deptPo);
        // 删除
        removeById(deptId);
        
        // 发布事件
        applicationContext.publishEvent(new SysDeptDeletedEvent(deptPo));
        applicationContext.publishEvent(new NeedDeleteHisEvent(Lists.newArrayList(deptId), SysDeptHisPO.class));
        return resp;
    }
    
    @Override
    @NotNull
    @TenantDataScope(TenantDataScopeBO.IGNORE_URD)
    public Map<String, String> mappingDeptNamePath(@Nullable Collection<String> deptIdPathList) {
        if (CollectionUtils.isEmpty(deptIdPathList)) {
            return Collections.emptyMap();
        }
        Map<String, String> pathNameMap = new HashMap<>(16);
        Set<Integer> deptIdSet = deptIdPathList.stream().flatMap(deptIdPath -> {
            if (StringUtils.isBlank(deptIdPath)) {
                return Stream.empty();
            }
            return Arrays.stream(deptIdPath.split(JdSymbolConstant.COMMA));
        }).filter(StringUtils::isNotBlank).map(Integer::parseInt).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(deptIdSet)) {
            return pathNameMap;
        }
        Map<String, String> idNameMap = list(new LambdaQueryWrapper<SysDeptPO>()
                .select(SysDeptPO::getId, SysDeptPO::getName)
                .in(SysDeptPO::getId, deptIdSet)
        )
        .stream()
        .filter(x -> x.getName() != null)
        .collect(Collectors.toMap(x -> x.getId().toString(), SysDeptPO::getName));
        
        for (String deptIdPath : deptIdPathList) {
            String deptNamePath = Arrays.stream(deptIdPath.split(JdSymbolConstant.COMMA))
                    .filter(StringUtils::isNotBlank)
                    .map(idNameMap::get).collect(Collectors.joining(JdSymbolConstant.COMMA));
            pathNameMap.put(deptIdPath, deptNamePath);
        }
        return pathNameMap;
    }
    
    @Override
    @Nullable
    @TenantDataScope(value = TenantDataScopeBO.IGNORE_URD)
    public SysDeptPO getByPath(@Nullable String path) {
        if (StringUtils.isBlank(path)) {
            return null;
        }
        return getOne(new LambdaQueryWrapper<SysDeptPO>()
                .select(SysDeptPO::getId)
                .eq(SysDeptPO::getPath, path)
        );
    }
    
    @NotNull
    private SysDeptDetailRespVO detail(SysDeptPO po) {
        final SysDeptDetailRespVO resp = new SysDeptDetailRespVO();
        BeanUtils.copyProperties(po, resp);
        // 填充父部门名称
        Integer pid = resp.getPid();
        if (pid != null && pid != 0) {
            SysDeptPO parentDept = getOne(new LambdaQueryWrapper<SysDeptPO>()
                    .select(SysDeptPO::getName)
                    .eq(SysDeptPO::getId, pid)
            );
            if (parentDept != null) {
                resp.setPName(parentDept.getName());
            }
        }
    
        String path = resp.getPath();
        if (StringUtils.isNotBlank(path)) {
            // 处理 path
            String tenant = JdContextUtil.currUserTenantNonNull();
            boolean isBelongBizTenant = sysTenantService.belongBizTenant(tenant);
            String currPath = SysUserUtil.shrinkDeptPathIfNecessary(tenant, isBelongBizTenant, tenantProperties.getBizTenantLevel(), Lists.newArrayList(path)).get(path);
            resp.setPath(currPath);
            log.info("update path '{}' -> currPath '{}'", path, currPath);
    
            // 处理 namePath
            Map<String, String> pathNameMap = mappingDeptNamePath(Lists.newArrayList(currPath));
            resp.setNamePath(pathNameMap.get(currPath));
        }
        return resp;
    }
}
